package org.greatlogic.gwtfilereaderwidget.client.itunes;

import com.google.gwt.xml.client.Document;
import com.google.gwt.xml.client.Element;
import com.google.gwt.xml.client.XMLParser;
import com.google.web.bindery.event.shared.EventBus;
import java.util.TreeMap;
import java.util.TreeSet;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesEnums.EITunesEventType;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesEnums.ETrackKeyType;
/*
 * Copyright 2006 Andy King (GreatLogic.com)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
public class ITunesLibrary {
//--------------------------------------------------------------------------------------------------
private EventBus                  _eventBus;
private TreeMap<String, Playlist> _playlistByNameMap;
private TreeMap<Integer, Track>   _trackByTrackIDMap;
private int                       _trackProgressEventIncrement;
//--------------------------------------------------------------------------------------------------
/**
 * @param eventBus An optional event bus that will be used for progress events. If this is null then
 * progress events will not be fired.
 * @param trackProgressEventIncrement The number of tracks between progress events.
 */
public ITunesLibrary(final EventBus eventBus, final int trackProgressEventIncrement) {
  _eventBus = eventBus;
  _trackProgressEventIncrement = trackProgressEventIncrement;
} // ITunesLibrary()
//--------------------------------------------------------------------------------------------------
void addTrack(final Track track) {
  _trackByTrackIDMap.put(track.getTrackID(), track);
} // addTrack()
//--------------------------------------------------------------------------------------------------
public Playlist getMasterPlaylist() throws ITunesException {
  Playlist result = null;
  for (Playlist playlist : _playlistByNameMap.values()) {
    if (playlist.getKeyAndValueEntry(ETrackKeyType.Master).getValueAsBoolean()) {
      result = playlist;
      break;
    }
  }
  if (result == null) {
    throw new ITunesException("No master playlist found");
  }
  return result;
} // getMasterPlaylist()
//--------------------------------------------------------------------------------------------------
public Playlist getPlaylistName(final String playlistName) {
  return _playlistByNameMap.get(playlistName);
} // getPlaylistByNameMap()
//--------------------------------------------------------------------------------------------------
public TreeMap<String, Playlist> getPlaylistByNameMap() {
  return _playlistByNameMap;
} // getPlaylistByNameMap()
//--------------------------------------------------------------------------------------------------
/**
 * Returns the Track object that is associated with the requested track ID.
 * @param trackID The requested track ID.
 * @return The Track object that is associated with the requested track ID, or a null value if there
 * is no associated track.
 */
Track getTrackByTrackID(final int trackID) {
  return _trackByTrackIDMap.get(trackID);
} // getTrackByTrackID()
//--------------------------------------------------------------------------------------------------
public TreeMap<Integer, Track> getTrackByTrackIDMap() {
  return _trackByTrackIDMap;
} // getTrackByTrackIDMap()
//--------------------------------------------------------------------------------------------------
/**
 * Returns a set containing all the tracks found in the requested play lists.
 * @param playlistNames An Iterable that contains the play list names. If this is null then all
 * tracks will be included in the set.
 * @return A set containing all the tracks found in the requested play lists.
 */
public TreeSet<Track> getTrackListSetUsingPlaylistNames(final Iterable<String> playlistNames)
  throws ITunesException {
  TreeSet<Track> result = new TreeSet<Track>();
  Iterable<String> localPlaylistNames = playlistNames;
  if (localPlaylistNames == null) {
    localPlaylistNames = new TreeSet<String>();
    ((TreeSet<String>)localPlaylistNames).add(getMasterPlaylist().getName());
  }
  for (String playlistName : localPlaylistNames) {
    Playlist playlist = _playlistByNameMap.get(playlistName);
    if (playlist != null) {
      for (Track track : playlist.getTrackSet()) {
        result.add(track);
      }
    }
  }
  return result;
} // getTrackListSetUsingPlaylistNames()
//--------------------------------------------------------------------------------------------------
/**
 * Loads the library from the supplied string.
 * @param xmlString The iTunes library XML.
 */
public void load(final String xmlString) {
  ITunesUtil.setEventBus(_eventBus);
  try {
    _playlistByNameMap = new TreeMap<String, Playlist>();
    _trackByTrackIDMap = new TreeMap<Integer, Track>();
    ITunesUtil.fireEvent(EITunesEventType.LoadBegin);
    ITunesUtil.fireEvent(EITunesEventType.ParsingBegin);
    int plistIndex = xmlString.indexOf("<plist");
    if (plistIndex < 0) {
      throw new ITunesException("The library XML is not valid (there is no 'plist' element)");
    }
    Document document = XMLParser.parse(xmlString.substring(plistIndex));
    ITunesUtil.fireEvent(EITunesEventType.ParsingEnd);
    ITunesUtil.fireEvent(EITunesEventType.TrackLoadingBegin);
    Element plistElement = document.getDocumentElement();
    Element topLevelDictElement = ITunesUtil.getFirstChildElement(plistElement);
    ITunesUtil.validateElementName(topLevelDictElement, "dict", "First 'dict' element expected");
    // Find the first "dict" node - this is the parent node for the tracks
    Element tracksParentElement = ITunesUtil.getFirstChildElement(topLevelDictElement, "dict",
                                                                  null,
                                                                  "No 'dict' element found for tracks");
    loadTracks(tracksParentElement, _trackProgressEventIncrement);
    ITunesUtil.fireEvent(EITunesEventType.TrackLoadingEnd);
    // Find the "Playlists" node
    ITunesUtil.fireEvent(EITunesEventType.PlaylistLoadingBegin);
    Element playlistsElement = ITunesUtil.getFirstChildElement(topLevelDictElement, "key",
                                                               "Playlists", null);
    if (playlistsElement != null) {
      Element playlistsArrayElement = ITunesUtil.getNextSiblingElement(playlistsElement);
      ITunesUtil.validateElementName(playlistsArrayElement, "array",
                                     "Playlists 'array' element expected");
      loadPlaylists(playlistsArrayElement);
    }
    ITunesUtil.fireEvent(EITunesEventType.PlaylistLoadingEnd);
    if (KeyAndValueEntry.getUnknownKeyTypeSet().size() > 0 ||
        KeyAndValueEntry.getUnknownKeyDataTypeSet().size() > 0) {
      ITunesUtil.fireEvent(EITunesEventType.UnknownKeyAndDataTypes,
                           "There were unknown key types and/or key data types",
                           KeyAndValueEntry.getUnknownKeyTypeSet(),
                           KeyAndValueEntry.getUnknownKeyDataTypeSet());
    }
    ITunesUtil.fireEvent(EITunesEventType.LoadEnd);
    ITunesUtil.validateElementName(plistElement, "plist", "'plist' element expected");
  }
  catch (ITunesException ite) {
    ITunesUtil.fireEvent(EITunesEventType.Error, ite.getMessage());
  }
} // load()
//--------------------------------------------------------------------------------------------------
/**
 * Loads the playlists.
 * @param playlistsArrayElement The "array" element that is the parent to all playlists.
 */
private void loadPlaylists(final Element playlistsArrayElement) throws ITunesException {
  Element playlistDictElement = ITunesUtil.getFirstChildElement(playlistsArrayElement, "dict");
  while (playlistDictElement != null) {
    Playlist playlist = new Playlist(this, playlistDictElement);
    _playlistByNameMap.put(playlist.getName(), playlist);
    playlistDictElement = ITunesUtil.getNextSiblingElement(playlistDictElement, "dict");
  }
} // loadPlaylists()
//--------------------------------------------------------------------------------------------------
/**
 * Loads the track elements into the track map.
 * @param tracksDictElement The "dict" element that is the parent to all tracks.
 * @param trackProgressEventIncrement The number of tracks between progress events.
 */
private void loadTracks(final Element tracksDictElement, final int trackProgressEventIncrement)
  throws ITunesException {
  Element trackDictElement = ITunesUtil.getFirstChildElement(tracksDictElement, "dict");
  int numberOfTracks = 0;
  while (trackDictElement != null) {
    Track track = new Track(trackDictElement);
    _trackByTrackIDMap.put(track.getTrackID(), track);
    ++numberOfTracks;
    if (trackProgressEventIncrement > 0 && numberOfTracks % trackProgressEventIncrement == 0) {
      ITunesUtil.fireEvent(EITunesEventType.TrackProgress, null, -1, numberOfTracks);
    }
    trackDictElement = ITunesUtil.getNextSiblingElement(trackDictElement, "dict");
  }
} // loadTracks()
//--------------------------------------------------------------------------------------------------
}